﻿using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;

namespace StockSharp.AlfaDirect.Tables
{
    /// <summary>
    /// Обычная таблица альфы. Все таблицы имеют такой тип кроме стакана. Особенности в том что не поддерживается
    /// многократная подписка с разными фильтрами, а так же многократная отписка тоже не поддерживается.
    /// </summary>
    public class AdTable : Table
    {
        /// <summary>
        /// Конструктор класса. Для реальной работы с таблицами, передаем классу делегаты, через которые объект будет работать
        /// с реальными таблицами и получать данные.
        /// </summary>
        /// <remarks>Все входные параметры проверяются на корректность. Гарантируется что делегаты не равны null</remarks>
        /// <param name="columns">Список колонок, которые будут содержаться в псевдотаблице.</param>
        /// <param name="tableSubscriber">Метод подписывающий нас на таблицу (name, fields, where)</param>
        /// <param name="tableUnSubscriber">Метод отписывающий нас от таблицы</param>
        /// <param name="tableReader">Метод читающий данные из таблицы</param>
        /// <param name="name">Имя реальной таблицы Альфы, из которой получать данные в псевдотаблицу.</param>
        /// <exception cref="ArgumentNullException">Вызывается при проверке аргументов метода.</exception>
        public AdTable(string name, 
                       List<string> columns, 
                       Action<string, IList<string>, FilterRules> tableSubscriber, 
                       Action<string, FilterRules> tableUnSubscriber, 
                       Func<string, IList<string>, FilterRules, object> tableReader) 
                                : base(name, columns, tableSubscriber, tableUnSubscriber, tableReader)
        {
        }

        /// <summary>
        /// Подписывает на таблицу с указанным фильтром. Добавляет нового подписчика в список подписчиков таблицы.
        /// </summary>
        /// <param name="subscriber">Подписчик, который желает подписаться на эту таблицу.</param>
        public override void Subscribe(Subscriber subscriber)
        {
            // обычная таблица Альфы не понимает множественных подписок и тупо перебивает одна другую.
            // поэтому если подписка уже есть, не делаем еще одну фактически, просто добавляем подписчика.
            if (_subscribes.Count == 0)
            {
                _tableSubscriber(Name, Columns, subscriber.Where);
            }

            // если подписка не удалась, выше будет вызвано исключение.
            // если удалась, обновляем список подписок на таблицу.
            lock (_subscribes.SyncRoot)
            {
                _subscribes.Add(subscriber);
            }
        }
        /// <summary>
        /// Отписывает подписчика от таблицы. После этого он перестает получать события обновления таблицы.
        /// Если подписчика не существует среди тех кто подписан на таблицу ничего не произойдет.
        /// </summary>
        /// <param name="subscriber">Подписчик которого нужно отписать от таблицы.</param>
        public override void UnSubscribe(Subscriber subscriber)
        {
            lock (_subscribes.SyncRoot)
            {
                // обновляем список подписок на таблицу.
                _subscribes.Remove(subscriber);
            }

            // пробуем отписаться от таблицы только в случае если подписчиков не осталось. 
            // в остальных случаях не отписываемся иначе надо будет переподписываться в связи с тем, 
            // что одна отписка сразу снимает все подписки. Отличается таблица queue другим поведением.
            if (_subscribes.Count == 0)
            {
                _tableUnSubscriber(Name, subscriber.Where);
            }
        }
        /// <summary>
        /// Отписывает указанных подписчиков от таблицы. После этого они перестанут получать сообщения об измененных данных таблицы.
        /// Для отписывания группы подписок рекомендуется использовать данный метод, он гораздо быстрее чем отписка по одиночке.
        /// </summary>
        /// <param name="subscribers">Список подписчиков.</param>
        public void UnSubscribe(IEnumerable<Subscriber> subscribers)
        {
            List<Subscriber> list;
            lock (_subscribes.SyncRoot)
            {
                list = _subscribes.ToList();
            }

            // Используем преобразование коллекции в массив, для того чтобы избежать Исключения.
            // Во время перебора коллекции мы изменяем содержимое коллекции.
            foreach (var subscriber in list)
            {
                UnSubscribe(subscriber);
            }
        }


        /// <summary>
        /// Парсит данные полученные в таблицу. 
        /// </summary>
        /// <param name="dataPortion">Порция данных</param>
        /// <returns>Распарсенные данные.</returns>
        protected override TableData ParseDataPortion(TableDataPortion dataPortion)
        {
            // проверим входные параметры
            if (dataPortion.IsNull())
                throw new ArgumentNullException("dataPortion");



            var data = (string)dataPortion.Data;

            // Обработаем каждую строку. Распарсим ее и значения запихаем в выходной список словарей.
            // Будем учитывать что не все поля могут быть в строке данных. Могут быть пустые поля, а часть может вообще не быть.
            // Таким образом число колонок может быть меньше чем исходное число полей.
            var result = new TableData();

            if (string.IsNullOrWhiteSpace(data))
                return result;

            // данные будут приходить в виде пачки строк. В каждоый строке поля разделены '|'
            var raws = data.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

            foreach (var raw in raws)
            {
                var resRaw = new TableRaw();

                // 83487901|41469-000|42550|M|B|8356|1|1|0:00:00||FORTS|0|
                // логично что после разбиения, элементов всегда будет больше чем реальное число колонок, за счет последнего пустого.
                var columns = raw.Split('|');

                // если число колонок не совпадает с числом полученных полей минус последнее пустое поле, вызовем ошибку и пропускаем
                // обработку кривых данных
                if (columns.Length - 1 != Columns.Count)
                {
                    RaiseParseDataErrorEvent(new ParseDataErrorException(message: "Число полей в полученных данных отличется от ожидаемого {1}.\r\n {0}".Put(raw, Columns.Count),
                                                                          columns: this.Columns,
                                                                          wrongData: raw));
                    continue;
                }

                // заполняем TableRaw структуру данными
                for (var i = 0; i < Columns.Count; i++)
                {
                    resRaw.Add(Columns[i], columns[i]);
                }

                result.Add(resRaw);
            }

            return result;
        }
    }
}
